package gu.simplemq.redis;

import java.net.URI;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Set;
import com.google.common.base.Objects;
import com.google.common.base.Optional;
import com.google.common.base.Predicate;
import com.google.common.base.Predicates;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Iterables;
import com.google.common.collect.Maps;
import gu.simplemq.Constant;
import gu.simplemq.json.BaseJsonEncoder;
import gu.simplemq.pool.BaseMQPool;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;
import redis.clients.jedis.Protocol;
import redis.clients.util.JedisURIHelper;

/**
 * 延迟初始化的 JedisPool封装类（线程安全）,使用方法:<br>
 * 通过 {@link #getDefaultInstance()} 和getInstance(...)系列静态方法获取{@link JedisPoolLazy}实例<br>
 * 通过{@link #apply()} 和 {@link #free()}方法实现{@link Jedis}对象的申请和释放
 * @author guyadong
 *
 */
public class JedisPoolLazy extends BaseMQPool<Jedis> implements Constant{
	/** {@link JedisPoolLazy }实例集合 */
	private static final Set<JedisPoolLazy> POOL_SET = Collections.synchronizedSet(new LinkedHashSet<JedisPoolLazy>());
	static {
		// 程序退出时自动销毁连接池对象
		Runtime.getRuntime().addShutdownHook(new Thread(){
			@Override
			public void run() {
				try {
					closeAll();	
				} catch (Throwable e) {
					logger.error(e.getMessage(),e);
				}
				
			}});
	}

	/** {@link JedisPoolLazy} 初始化参数名 */
	public static enum PropName{
		/** 线程配置参数对象,类型:{@link JedisPoolConfig} */jedisPoolConfig(JedisPoolConfig.class),
		/** 主机名,类型: {@link String}*/host,
		/** 端口号,类型: {@link Integer} */port(Integer.class),
		/** REDIS密码,类型: {@link String} */password,
		/** 数据库ID,类型: {@link Integer}  */database(Integer.class),
		/** 访问超时(毫秒),类型: {@link Integer} */timeout(Integer.class),
		/** 访问地址,类型: {@link URI}*/uri(URI.class);
		public final Class<?>type;
		private PropName(){
			this(String.class);
		}
		private PropName(Class<?> type){
			this.type = type;
		}
		public Object parse(Object input){
			if(input == null || type.isInstance(input)){
				return input;
			}
			if(Number.class.isAssignableFrom(type) && input instanceof Number){
				if(Integer.class.equals(type)){
					return ((Number)input).intValue();
				}
				if(Long.class.equals(type)){
					return ((Number)input).longValue();
				}
				if(Short.class.equals(type)){
					return ((Number)input).shortValue();
				}
				if(Byte.class.equals(type)){
					return ((Number)input).byteValue();
				}
				if(Float.class.equals(type)){
					return ((Number)input).floatValue();
				}
				if(Double.class.equals(type)){
					return ((Number)input).doubleValue();
				}
			}
			if(input instanceof String){
				return parse((String)input);
			}
			throw new UnsupportedOperationException("INVALID input type " + input.getClass().getName());
		}
		public Object parse(String input){
			if(String.class.equals(type)){
				return input;
			}
			if(Integer.class.equals(type)){
				return null == input ? null : Integer.valueOf(input);
			}
			if(URI.class.equals(type)){
				return null == input ? null : URI.create(input);
			}
			if(JedisPoolConfig.class.equals(type)){
				return null == input ? null : BaseJsonEncoder.getEncoder().fromJson(input, JedisPoolConfig.class);
			}
			throw new UnsupportedOperationException("UNSUPPORTED type " + type.getName());
		}
		public String toValueString(Object value){
			if(value instanceof JedisPoolConfig){
				return BaseJsonEncoder.getEncoder().toJsonString(value);
			}			
			return null == value ? null : value.toString();
		}
	}
	
	public static final JedisPoolConfig DEFAULT_CONFIG = new JedisPoolConfig() {
		{
			setMaxTotal(Runtime.getRuntime().availableProcessors());
		}
	};
	/** 
	 * redis缺省连接参数<br>
	 * 这里没有使用guava的ImmutableMap，因为HashMap允许Value为null, ImmutableMap不允许 
	 **/
	public static final Map<PropName, Object> DEFAULT_PARAMETERS = Collections.unmodifiableMap(new HashMap<PropName, Object>() {
		private static final long serialVersionUID = 1L;
		{
			put(PropName.jedisPoolConfig, DEFAULT_CONFIG);
			put(PropName.host, Protocol.DEFAULT_HOST);
			put(PropName.port, Protocol.DEFAULT_PORT);
			put(PropName.password, null);
			put(PropName.database, Protocol.DEFAULT_DATABASE);
			put(PropName.timeout, Protocol.DEFAULT_TIMEOUT);
		}
	});
	/** 默认连接池实例 */
	private static volatile JedisPoolLazy defaultInstance;
	
	/**
	 * 返回默认实例,如果 {@link #defaultInstance}为null则创建默认实例
	 * @return
	 * @see #createDefaultInstance(Map)
	 */
	public static JedisPoolLazy getDefaultInstance() {
		return null == defaultInstance
				? createDefaultInstance(null) 
				: defaultInstance;
	}

	/**
	 * 设置默认{@link JedisPoolLazy}实例<br>
	 * 仅当默认实例未初始化(为{@code null})且输入参数不为{@code null}时有效(返回{@code true})
	 * 如果默认实例已经初始化,则输出警告日志,返回{@code false}
	 * @param poolLazy 为{@code null}返回{@code false}
	 * @return  设置成功返回{@code true},否则返回{@code false}
	 */
	public static boolean setDefaultInstance(JedisPoolLazy poolLazy) {
		// double checking
		if(null == defaultInstance){
			synchronized(JedisPoolLazy.class){
				if(null == defaultInstance && null != poolLazy){
					defaultInstance = poolLazy;
					return true;
				}
			}
		}
		logger.warn("INVALID INVOCATION,default instance was initialized already before this invocation");
		return false;
	}

	/**
	 * 检测默认实例是否初始化
	 * @return 默认实例已经初始化则返回{@code true},否则返回false
	 */
	public static boolean defaultInstanceInitialized(){
		return defaultInstance != null;
	}
	/**
	 * 根据指定的连接参数创建默认实例,只能被调用一次(线程安全)
	 * @param props
	 * @return
	 */
	public static final JedisPoolLazy createDefaultInstance(Map<PropName,Object> props){
		setDefaultInstance( getInstance(props,true));
		return defaultInstance;
	}
	
	static JedisPoolLazy createInstance(Map<PropName,Object> props) {
		return new JedisPoolLazy(props);
	}
	/** 
	 * 根据{@code props}提供的参数及缺省参数{@link DEFAULT_PARAMETERS}创建一组完整的初始化参数<br>
	 * 如果props中定义了uri，则优先使用uri提供的参数
	 * @param props
	 * @deprecated move to {@link JedisUtils}
	 */
	public static HashMap<PropName,Object> initParameters(Map<PropName,Object> props){
		// 初始化时复制一份缺省参数
		HashMap<PropName,Object> params = Maps.newHashMap(DEFAULT_PARAMETERS);
		if(null != props){			
			// 过滤掉所有为null的参数，避免将缺省参数覆盖为null
			Map<PropName, Object> filtered = Maps.filterValues(props, Predicates.notNull());
			// 缺省参数与输入参数合并
			params.putAll(filtered);
			URI uri = (URI) props.get(PropName.uri);
			if(uri != null){
				uri = new JedisURI(uri).getUri();
				params.put(JedisPoolLazy.PropName.uri, uri);
				if(uri.getHost() != null){
					params.put(PropName.host, uri.getHost());
				}
				if(uri.getPort() != -1){
					params.put(PropName.port, uri.getPort());
				}
				int dbIndex = JedisURIHelper.getDBIndex(uri);
				if(dbIndex != 0){
					params.put(PropName.database, dbIndex);
				}
				String pwd = JedisURIHelper.getPassword(uri);
				if(pwd != null){
					params.put(PropName.password, pwd);
				}
			}
		}
		return params;
	}
	
	/**
	 * 查找在连接池对象集合中查找对应的匹配的对象,找不到就创建新实例
	 * @param props
	 * @param fullMatch 是否要求所有参数都匹配，为false时只匹配uri,相同就算找到
	 * @return
	 */
	public static synchronized JedisPoolLazy getInstance(Map<PropName,Object> props, boolean fullMatch) {
		// 初始化时复制一份缺省参数
		final HashMap<PropName,Object> params = JedisUtils.initParameters(props);
		// 查找在连接池对象集合中查找对应的匹配的对象,找不到就创建新实例
		final URI canonicalURI = JedisUtils.getCanonicalURI(params);
		Optional<JedisPoolLazy> found;
		if(fullMatch){
			// 全匹配
			found = Iterables.tryFind(POOL_SET, new Predicate<JedisPoolLazy>() {

				@Override
				public boolean apply(JedisPoolLazy input) {
					return Objects.equal(canonicalURI, input.getCanonicalURI())
							&& Objects.equal(params.get(PropName.jedisPoolConfig), input.parameters.get(PropName.jedisPoolConfig))
							&& Objects.equal(params.get(PropName.timeout), input.parameters.get(PropName.timeout));
				}
			});
		}else{
			// 只匹配uri,相同就算找到
			found = Iterables.tryFind(POOL_SET, new Predicate<JedisPoolLazy>() {

				@Override
				public boolean apply(JedisPoolLazy input) {
					return Objects.equal(canonicalURI, input.getCanonicalURI());
				}
			});
		}
		return found.or(createInstance(params));
	
	}
	
	private static JedisPoolLazy getInstance( JedisPoolConfig jedisPoolConfig, String host, int port, final String password,
			int database, int timeout, URI uri){
		HashMap<PropName,Object> param = new HashMap<PropName,Object>(DEFAULT_PARAMETERS);
		if(null != jedisPoolConfig){
			param.put(PropName.jedisPoolConfig, jedisPoolConfig);
		}
		if(null != host && !host.isEmpty()){
			param.put(PropName.host, host);
		}
		if(0 < port){
			param.put(PropName.port, port);
		}
		param.put(PropName.password, password);
		if(0 <= database){
			param.put(PropName.database, database);
		}
		if( 0 < timeout ){
			param.put(PropName.timeout, timeout);
		}
		param.put(PropName.uri, uri);
		return getInstance(param, true);
	}
	
	public static JedisPoolLazy getInstance(JedisPoolConfig jedisPoolConfig, URI uri, int timeout) {
		if(null == uri){
			throw new NullPointerException(" the 'uri' must not be null");
		}
		return getInstance(jedisPoolConfig,null,0,null,-1,timeout,uri);
	}
	
	/**
	 * 根据uri查找在连接池对象集合中查找对应的匹配的对象,找不到就创建新实例
	 * @param uri
	 * @return
	 */
	public static JedisPoolLazy getInstance(URI uri) {
		return getInstance(ImmutableMap.of(PropName.uri, (Object)uri), false);
	}
	
	public static JedisPoolLazy getInstance( JedisPoolConfig jedisPoolConfig, String host, int port, final String password,
			int database, int timeout){
		return getInstance(jedisPoolConfig,host,port,password,database,timeout, null);
	}
	
	public static JedisPoolLazy getInstance(String host, int port, final String password, int database) {
		return getInstance(DEFAULT_CONFIG,host,port,password,database,Protocol.DEFAULT_TIMEOUT);
	}

	public static JedisPoolLazy getInstance(String host, int port) {
		return getInstance(host,port,null,Protocol.DEFAULT_DATABASE);
	}
	
	public static  JedisPoolLazy getInstance(String host) {
		 return getInstance(host,Protocol.DEFAULT_PORT);
	}
	public static  JedisPoolLazy getInstanceByURI(String uri) {
		 return getInstance(URI.create(uri));
	}
	private final Map<PropName,Object> parameters;
	
	public Map<PropName, Object> getParameters() {
		return new HashMap<PropName,Object>(parameters);
	}

	private volatile JedisPool pool;
	private static final boolean jmxEnable = isJmxEnable();

	protected JedisPoolLazy (Map<PropName,Object> props) {
		this.parameters=JedisUtils.initParameters(props);
		POOL_SET.add(this);
	}
	
	/**
	 * 将当前实例指定为默认实例
	 * @return
	 * @see #setDefaultInstance(JedisPoolLazy)
	 */
	public JedisPoolLazy asDefaultInstance(){
		setDefaultInstance(this);
		return this;
	}
	private JedisPool createPool(){
		JedisPool pool;
		int timeout = (Integer)parameters.get(PropName.timeout);
		URI uri = this.getCanonicalURI();
		JedisPoolConfig config = (JedisPoolConfig) parameters.get(PropName.jedisPoolConfig);
		// 指定jmxEnable,解决 android 下异常，android不支持JMX
		config.setJmxEnabled(jmxEnable);
		pool = new JedisPool(
				config,
				uri, 
				timeout);
		logger.info("jedis pool initialized(连接池初始化)  {} timeout : {} ms",uri,timeout);
		return pool;
	}

	@Override
	public Jedis borrow(){
		// double-checked locking
		if(null == pool){
			synchronized (this){
				if(null == pool){
					pool = createPool();
				}
			}
		}
        return pool.getResource();
    }
    
	@Override
    public void release(Jedis r) {
        if (r != null){
            r.close();
        }
    }
	@Override
	public URI getCanonicalURI(){
		return JedisUtils.getCanonicalURI(parameters);
	}
	
	
	/**
	 * 判断JVM是否支持JMX，android下返回{@code false}
	 * @return
	 */
	private static boolean isJmxEnable(){
		try{
			Class.forName("java.lang.management.ManagementFactory");
			return true;
		}catch(ClassNotFoundException e){
			return false;
		}
	}
	
	@Override
	public void close(){
		// double check
		if(pool != null){
			synchronized (this) {
				if(pool != null){
					pool.close();
					logger.info("discard jedis pool: {}",this);
					POOL_SET.remove(this);
					pool = null;
					closed = true;
				}
			}
		}
	}
	/**
	 * 关闭并删除所有资源池中的{@link JedisPoolLazy}实例
	 */
	public synchronized static void closeAll(){
		for(Iterator<JedisPoolLazy> itor = POOL_SET.iterator();itor.hasNext();){
			JedisPoolLazy p = itor.next();
			itor.remove();
			p.close();
		}
	}
}
